package com.appistry.storage.rest.impl;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.URLEncoder;

import com.appistry.api.rest.RestConnection;
import com.appistry.api.rest.domain.Credentials;
import com.appistry.api.rest.domain.RestResource;
import com.appistry.api.rest.domain.RestResponse;
import com.appistry.api.rest.exception.RestException;
import com.appistry.api.rest.impl.HttpRestConnection;
import com.appistry.api.rest.impl.HttpsRestConnection;
import com.appistry.api.util.Version;
import com.appistry.storage.rest.RestStorageManager;
import com.appistry.storage.rest.domain.ControllerAction;

public class RestStorageManagerImpl implements RestStorageManager {
	private static final String RESOURCE_WORKERS = "/workers";
	private static final String RESOURCE_FILES = "/files";
	private static final String RESOURCE_FILELIST = "/filelist";
	private static final String RESOURCE_LOCATION = "/location";
	private static final String RESOURCE_METADATA = "/metadata";
	private static final String RESOURCE_TERRITORIES = "/territories";
	private static final String RESOURCE_CONFIG = "/config";
	private static final String RESOURCE_STATUS = "/status";
	private static final String RESOURCE_TERRITORY = "/territory";
	private static final String RESOURCE_CONTROLLER = "/controller";
	
	private static final String VAR_TERRITORY = "territory";
	private static final String VAR_VERSION = "version";
	private static final String VAR_OFFSET = "offset";
	private static final String VAR_LENGTH = "length";
	private static final String VAR_SHOWMETADATA = "showMetadata";
	private static final String VAR_SHOWDELETED = "showDeleted";
	private static final String VAR_MINREADCOPIES = "minreadcopies";
	private static final String VAR_COPIES = "copies";
	private static final String VAR_MINCOPIES = "mincopies";
	
	private static final String DEFAULT_URL_ENCODING = "UTF-8";
	private static final String FORWARD_SLASH = "/";
	private static final String URL_ENCODED_FORWARD_SLASH = "%2F";
	
	private RestConnection restConnection;
	
	//TODO finish implementation of versioning
	private final Version version;

	public RestStorageManagerImpl(InetAddress address, int port, Credentials credentials, Version version) {
		this(address, port, false, credentials, version);
	}

	public RestStorageManagerImpl(InetAddress address, int port, boolean isSSL, Credentials credentials, Version version) {
		if(isSSL){
			restConnection = new HttpsRestConnection(address, port, credentials);
		}else{
			restConnection = new HttpRestConnection(address, port, credentials);
		}
		this.version = version;
	}
	
	public RestResponse addWorkerResource(InetAddress ip) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress());
		
		return restConnection.put(resource.getResourceUrl(), new ByteArrayInputStream(new byte[0]));
	}

	public RestResponse addWorkerResource(InetAddress ip, String territoryId) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress());
		resource.putKeyValuePair(VAR_TERRITORY, territoryId);
		
		return restConnection.put(resource.getResourceUrl(), new ByteArrayInputStream(new byte[0]));
	}

	public RestResponse deleteFile(String filename) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));		
		
		return restConnection.delete(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename, int minReadCopies) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_MINREADCOPIES, minReadCopies);		
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename, String version) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_VERSION, version);		
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename, String version, int minReadCopies) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_VERSION, version);
		resource.putKeyValuePair(VAR_MINREADCOPIES, minReadCopies);
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename, long offset, long length) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_OFFSET, offset);
		resource.putKeyValuePair(VAR_LENGTH, length);
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename, int minReadCopies, long offset, long length) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_MINREADCOPIES, minReadCopies);
		resource.putKeyValuePair(VAR_OFFSET, offset);
		resource.putKeyValuePair(VAR_LENGTH, length);
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFile(String filename, String version, int minReadCopies, long offset, long length) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_MINREADCOPIES, minReadCopies);
		resource.putKeyValuePair(VAR_OFFSET, offset);
		resource.putKeyValuePair(VAR_LENGTH, length);
		resource.putKeyValuePair(VAR_VERSION, version);
		
		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFileLocation(String filename) throws RestException {
		RestResource resource = new RestResource(RESOURCE_LOCATION, getUrlEncodedFilePath(filename));

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getFileMetadata(String filename) throws RestException {
		RestResource resource = new RestResource(RESOURCE_METADATA, getUrlEncodedFilePath(filename));

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getTerritories() throws RestException {
		RestResource resource = new RestResource(RESOURCE_TERRITORIES);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getTerritoryWorkers(String territoryId) throws RestException {
		RestResource resource = new RestResource(RESOURCE_TERRITORIES, territoryId);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getWorkerConfiguration(InetAddress ip) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress(), RESOURCE_CONFIG);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getWorkerResource(InetAddress ip) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress());

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getWorkerStatus(InetAddress ip) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress(), RESOURCE_STATUS);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse getWorkerTerritory(InetAddress ip) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress(), RESOURCE_TERRITORY);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse listFiles(String filepath) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILELIST, getUrlEncodedFilePath(filepath));

		return restConnection.get(resource.getResourceUrl());
	}
	
	public RestResponse listFilesWithMetaData(String filepath) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILELIST, getUrlEncodedFilePath(filepath));
		resource.putKeyValuePair(VAR_SHOWMETADATA, true);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse listWorkers() throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS);

		return restConnection.get(resource.getResourceUrl());
	}

	public RestResponse listWorkers(boolean showDeleted) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS);
		resource.putKeyValuePair(VAR_SHOWDELETED, showDeleted);

		return restConnection.get(resource.getResourceUrl());
	}
	
	public RestResponse putFile(String filename, long length, InputStream inputstream) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		
		//TODO do i really need this?
		resource.putKeyValuePair(VAR_COPIES, 2);
		resource.putKeyValuePair(VAR_MINCOPIES, 1);
		resource.putKeyValuePair(VAR_MINREADCOPIES, 1);

		return restConnection.put(resource.getResourceUrl(), length, inputstream);
	}

	//TODO Evaluate defaults (does Appistry already apply these or must I specify?)
	public RestResponse putFile(String filename, InputStream inputstream) throws RestException {		
		return putFile(filename, inputstream, 2, 1, 1);
	}

	public RestResponse putFile(String filename, InputStream inputstream, int copies, int minCopies, int minReadCopies) throws RestException {
		RestResource resource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		resource.putKeyValuePair(VAR_COPIES, copies);
		resource.putKeyValuePair(VAR_MINCOPIES, minCopies);
		resource.putKeyValuePair(VAR_MINREADCOPIES, minReadCopies);
		
		return restConnection.put(resource.getResourceUrl(), inputstream);
	}

	public RestResponse putWorkerTerritory(InetAddress ip, String territory) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress(), RESOURCE_TERRITORY);

		return restConnection.put(resource.getResourceUrl(), new ByteArrayInputStream(territory.getBytes()));
	}

	public RestResponse removeWorkerResource(InetAddress ip) throws RestException {
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress());

		return restConnection.delete(resource.getResourceUrl());
	}

	public RestResponse workerController(InetAddress ip, ControllerAction action) throws RestException {
		// Handle only these operations
		switch (action) {
		case START:
		case STOP:
			break;
		default:
			throw new UnsupportedOperationException("Action not supported: " + action);
		}
		
		RestResource resource = new RestResource(RESOURCE_WORKERS, ip.getHostAddress(), RESOURCE_CONTROLLER);

		return restConnection.put(resource.getResourceUrl(), new ByteArrayInputStream(action.toString().getBytes()));
	}

	public RestResponse moveFile(String filename, String dest) throws RestException {
		RestResource srcResource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(filename));
		RestResource destResource = new RestResource(RESOURCE_FILES, getUrlEncodedFilePath(dest));

		return restConnection.move(srcResource.getResourceUrl(), destResource.getResourceUrl());
	}

	/**
	 * Returns the file path encoded (except for '/')
	 * 
	 * <br>
	 * <br>
	 * For example:<br>
	 * Before: /path/file with space<br>
	 * After: /path/file+with+space
	 * 
	 * @param filename
	 * @return
	 */
	private static String getUrlEncodedFilePath(String filename) {
		try {
			String encoded = URLEncoder.encode(filename, DEFAULT_URL_ENCODING);
			String encodedExceptSlash = encoded.replaceAll(URL_ENCODED_FORWARD_SLASH, FORWARD_SLASH);

			return encodedExceptSlash;
		} catch (UnsupportedEncodingException e) {
			throw new RuntimeException("Unable to encode file path!", e);
		}
	}

	public RestConnection getRestConnection() {
		return restConnection;
	}

	public void setRestConnection(RestConnection restConnection) {
		this.restConnection = restConnection;
	}

}
